package ECC;

import java.math.*;
import java.util.*;
import java.io.*;

public class ECC {

    // Partes de un ECC.
    private static EllipticCurve curve;
    private Point generator;
    private static Point publicKey;
    private static BigInteger privateKey;

    // Necesitamos una curva, un punto generador (x,y) y una clave privada, na, que será 
    //utilizada para generar la clave pública.
    public ECC(EllipticCurve c, BigInteger x, BigInteger y, BigInteger nA) {

        curve = c;
        generator = new Point(curve, x, y);
        privateKey = nA;
        publicKey = generator.multiply(privateKey);
    }

    // Cifrado.
    public Point[] encrypt(Point plain) {

        // En primer lugar debemos elegir una k al azar, en el rango.
        int bits = curve.getP().bitLength();
        BigInteger k = new BigInteger(bits, new Random());

        // Nuestra salida es un par ordenado, (k*generator, plain + k*publickey)
        Point[] ans = new Point[2];
        ans[0] = generator.multiply(k);
        ans[1] = plain.add(publicKey.multiply(k));
        return ans;
    }

    // Descifrado. Similar a El Gamal.
    public Point decrypt(Point[] cipher) {

        // Esto es lo restamos.
        Point sub = cipher[0].multiply(privateKey);

        // Restar y devolver.
        return cipher[1].subtract(sub);
    }

    public String toString() {

        return "Gen: " + generator + "\n"
                + "pri: " + privateKey + "\n"
                + "pub: " + publicKey;
    }

    public static void ECC() throws IOException {
        int i;
        long comienzo, fin, comienzo1, fin1, tiempo, tiempo1;

        System.out.println("\nAplicación del metodo de cifrado con Curvas Elipticas (ECC)");
        System.out.println("\n\nTenemos que definir el tamaño de los números primos el tamaño esta definido en bytes");
        System.out.println("por ejemplo número 512 equivale a generar un número de 512 bytes");
        System.out.println("Por favor ingrese el tamaño deseado, recuerde a mayor tamaño, mayor seguridad, \npero mayor tiempo de ejecución: ");
        String sizeNumEntrada = (new BufferedReader(new InputStreamReader(System.in))).readLine();

        int sizeNum = Integer.parseInt(sizeNumEntrada);

        System.out.println("Tamaño de la clave: [" + sizeNum + "] bytes \n");

        // Just use the book's curve and test.
        EllipticCurve myCurve = new EllipticCurve(new BigInteger(sizeNum, 10, new Random()), new BigInteger("1"), new BigInteger("1"));
        BigInteger x = new BigInteger("6");
        BigInteger y = new BigInteger("19");
        BigInteger nA = new BigInteger(sizeNum, 10, new Random());
        ECC Alice = new ECC(myCurve, x, y, nA);

        System.out.println("Llave publica: [" + publicKey + "]");
        System.out.println("Llave privada: [" + privateKey + "]");

        System.out.println("\n Ingrese el texto a cifrar: ");
        String entrada = (new BufferedReader(new InputStreamReader(System.in))).readLine();

        byte[] arr = new byte[1];
        byte[] dig = entrada.getBytes();
        BigInteger[] bigdig = new BigInteger[dig.length];

        /**
         * En cada una de las posiciones del arreglo bigdig insertamos el byte
         * de la letra correspondiente, convirtiendolo a BigInteger
         */
        for (i = 0; i < bigdig.length; i++) {
            arr[0] = dig[i];
            bigdig[i] = new BigInteger(arr);
        }
        
        //Se crea un arreglo de puntos donde se guardaran el codigo ascii del texto ingresado
        Point[] puntos = new Point[bigdig.length];
        
        //Se crea un arrelgo de puntos donde se guardaran los puntos ya cifrados
        Point[] cifrados = new Point[bigdig.length];
        Point[] cifrados2 = new Point[bigdig.length];
        Point[] descifrados = new Point[bigdig.length];
        
        //Guarda en puntos las tuplas de puntos a cifrar con la curva, el ascii de la letra y un espacio
        for (i = 0; i < bigdig.length; i++) {
            puntos[i] = new Point(myCurve, bigdig[i], new BigInteger("0"));
        }
        System.out.println("\nLas coordenas generadas a partir de la entrada son: ");
        System.out.println(Arrays.toString(puntos) + "\n");
        comienzo = System.nanoTime();
        comienzo1 = System.currentTimeMillis();
        
        //Cifra cada tupla del arreglo de puntos
        for (i = 0; i < bigdig.length; i++) {
            Point aux = puntos[i];
            Point[] aux2 = Alice.encrypt(aux);
            cifrados[i] = aux2[0];
            cifrados2[i] = aux2[1];
        }
        fin = System.nanoTime();
        fin1 = System.currentTimeMillis();
        System.out.println("\nLas coordenas cifradas a partir de las coordenadas generadas son: ");
        System.out.println("Parte 1: "+Arrays.toString(cifrados));
        System.out.println("Parte 2: "+Arrays.toString(cifrados2) + "\n");
        tiempo = (fin - comienzo);
        tiempo1 = (fin1 - comienzo1);
        System.out.println("El proceso de cifrado se ejecutó en " + tiempo + " nanosegundos");
        System.out.println("Redondeado en " + tiempo1 + " milisegundos");
        
        //Descifra cada tupla de los arreglos cifrados y cifrados 2
        comienzo = System.nanoTime();
        comienzo1 = System.currentTimeMillis();
        for (i = 0; i < bigdig.length; i++) {
            Point[] ans = new Point[2];
            ans[0] = cifrados[i];
            ans[1] = cifrados2[i];
            Point descifrar = Alice.decrypt(ans);
            descifrados[i] = descifrar;
        }
        fin = System.nanoTime();
        fin1 = System.currentTimeMillis();
        System.out.println("\nLas coordenas descifradas a partir de las coordenadas cifradas son: ");
        System.out.println(Arrays.toString(descifrados) + "\n");
        tiempo = (fin - comienzo);
        tiempo1 = (fin1 - comienzo1);
        System.out.println("El proceso de descifrado se ejecutó en " + tiempo + " nanosegundos");
        System.out.println("Redondeado en " + tiempo1 + " milisegundos");

        char[] charArray = new char[descifrados.length];
        for (i = 0; i < descifrados.length; i++) {
            BigInteger aux = descifrados[i].acces(descifrados[i]);
            charArray[i] = (char) (aux.intValue());
        }
        String salida = new String(charArray);
        System.out.println("\nEl texto original recuperado es: ");
        System.out.println(salida);
    }
}